#include "config.h"
#include "iser.h"
#include "core.h"
#include "dbg.h"

static pthread_spinlock_t iser_sched_events_lock;
static int iser_sched_events_lock_init = 0;
static LIST_HEAD(iser_sched_events_list);
static __thread struct list_head private_iser_sched_events_list;

static void __attribute__((constructor)) _init(void)
{
        pthread_spin_init(&iser_sched_events_lock, PTHREAD_PROCESS_PRIVATE);
        iser_sched_events_lock_init = 1;
}

static int iser_sched_lock()
{
        YASSERT(iser_sched_events_lock_init);

        if (likely(core_self()))
                return 0;
        else
                return pthread_spin_lock(&iser_sched_events_lock);
}

static int iser_sched_unlock()
{
        if (likely(core_self()))
                return 0;
        else
                return pthread_spin_unlock(&iser_sched_events_lock);
}

struct list_head *get_iser_sched_events_list()
{
        if (core_self())
                return &private_iser_sched_events_list;
        else
                return &iser_sched_events_list;
}

void iser_sched_init_event(struct event_data *evt,
                          sched_event_handler_t sched_handler, void *data)
{
        evt->sched_handler = sched_handler;
        evt->scheduled = 0;
        evt->data = data;
        INIT_LIST_HEAD(&evt->e_list);
}

void iser_sched_add_event(struct event_data *evt)
{
        int ret = iser_sched_lock();
        YASSERT(!ret);

        if (!evt->scheduled) {
                evt->scheduled = 1;
                list_add_tail(&evt->e_list, get_iser_sched_events_list());
        }

        iser_sched_unlock();
}

void iser_sched_remove_event(struct event_data *evt)
{
        int ret = iser_sched_lock();
        YASSERT(!ret);

        if (evt->scheduled) {
                evt->scheduled = 0;
                list_del_init(&evt->e_list);
        }

        iser_sched_unlock();
}

inline void schedule_task_iosubmit(struct iser_task *task,
                                          struct iser_conn *conn)
{
    /*    list_add_tail(&task->exec_list, &conn->iosubmit_list);
        iser_sched_add_event(&conn->sched_iosubmit);

        DBUG("task:%p tag:0x%04"PRIx64 " cmdsn:0x%x\n",
                task, task->tag, task->cmd_sn);*/
        (void)conn;

        iser_scsi_cmd_iosubmit(task, 0);
}

void schedule_rdma_read(struct iser_task *task,
                                      struct iser_conn *conn)
{
        list_add_tail(&task->rdma_list, &conn->rdma_rd_list);
        iser_sched_add_event(&conn->sched_rdma_rd);

        DBUG("task:%p tag:0x%04"PRIx64 " cmdsn:0x%x\n",
                task, task->tag, task->cmd_sn);
}

void schedule_resp_tx(struct iser_task *task,
                                    struct iser_conn *conn)
{
        list_add_tail(&task->tx_list, &conn->resp_tx_list);
        iser_sched_add_event(&conn->sched_tx);

        DBUG("task:%p tag:0x%04"PRIx64 " cmdsn:0x%x\n",
                task, task->tag, task->cmd_sn);
}

void schedule_post_recv(struct iser_task *task,
                                      struct iser_conn *conn)
{
        list_add_tail(&task->recv_list, &conn->post_recv_list);
        iser_sched_add_event(&conn->sched_post_recv);

        DBUG("task:%p tag:0x%04"PRIx64 " cmdsn:0x%x\n",
                task, task->tag, task->cmd_sn);
}

int iser_exec_scheduled(void)
{
        struct list_head *last_sched;
        struct event_data *tev, *tevn;
        int work_remains = 0;

        int ret = iser_sched_lock();
        YASSERT(!ret);

        if (!list_empty(get_iser_sched_events_list())) {
                /* execute only work scheduled till now */
                last_sched = get_iser_sched_events_list()->prev;
                list_for_each_entry_safe(tev, tevn, get_iser_sched_events_list(), e_list) {
                        //iser_sched_remove_event(tev);
                        if (tev->scheduled) {
                                tev->scheduled = 0;
                                list_del_init(&tev->e_list);
                        }
                        iser_sched_unlock();
                        tev->sched_handler(tev);
                        ret = iser_sched_lock();
                        YASSERT(!ret);
                        if (&tev->e_list == last_sched)
                                break;
                }

                if (!list_empty(get_iser_sched_events_list()))
                        work_remains = 1;
        }

        iser_sched_unlock();

        return work_remains;
}

void iser_sched_init()
{
        INIT_LIST_HEAD(&private_iser_sched_events_list);
}
